fix: classify Android change notifications by MediaProvider flags on API 30+ - #1444
Merged
Merged
Conversation
…API 30+ MediaProvider tags row notifications with NOTIFY_INSERT/UPDATE/DELETE flags. Use them as the authoritative change type instead of the 30-second date heuristic. This also fixes inserts of rows that are not visible to the app yet — such as files pushed via adb push or desktop drag-and-drop, which stay pending and hidden — being misreported as deletations because the classification query cannot find the row. Verified on emulators: API 36 (push/insert/update/delete all classified correctly, one callback per change) and API 28 (legacy inference path intact). #1443
AlexV525
force-pushed
the
fix/1443-notify-misclassified-change-type
branch
from
August 21, 2026 15:13
40e7e64 to
ae2c775
Compare
Flutter stable (3.44+) auto-appends an analyzer 'exclude' section (build/android/ios/web/windows/macos/linux) to analysis_options.yaml during 'flutter pub get'. The Runnable workflow's format check runs 'git diff --exit-code .' right after pub get, so every PR and main itself have failed since the 3.47 stable rollout. Committing the generated section keeps the tree clean; also applies to the example options where the 3.44 tool writes the same section.
Flutter stable now enforces minimum versions at build time: Gradle 8.14 (was 8.13), AGP 8.11.1 (was 8.9.3), Kotlin 2.2.20 (was 2.1.20). The Runnable workflow's Android jobs build the example without the bypass flag, so they fail on every PR since the 3.47 rollout. Bump the wrapper, plugin and Kotlin versions accordingly, and commit the migrator-generated minSdkVersion/android.newDsl changes so the tree matches what the Flutter tool writes. Verified locally: flutter build apk --release (no bypass) and ./gradlew photo_manager:test both pass.
Reorder the migrator-generated analyzer exclude section so the file keeps the conventional shape: include first, one blank line between sections. The Flutter tool accepts this order and does not rewrite it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Closes #1443.
The issue reports that change callbacks never fire when a file is dragged from the desktop into an emulator, and proposes registering additional
ContentObservers on theexternal_primaryvolume URIs.Investigation on API 34 and API 36 emulators shows the diagnosis is different:
externalview for every row change (acceptWithExpansionin AOSP recurses intoVOLUME_EXTERNAL), so the existing observers do receive the event — verified viasetprop log.tag.MediaProvider VERBOSEanddumpsys content.external_primaryas well would produce duplicate callbacks for every regular write, since both volumes are notified.The actual failure: rows inserted via
adb push/ desktop drag-and-drop are owned bycom.android.shelland stayis_pending=1indefinitely, so they are not visible to the app.MediaObserver.onChangeclassifies the event by querying the row; the query comes back empty, and the code falls into the "row not found ⇒ delete" branch. The callback fires, but misreports the insert as a deletion.Fix
On API 30+, MediaProvider tags row notifications with
NOTIFY_INSERT/NOTIFY_UPDATE/NOTIFY_DELETEflags (observed values on an API 36 emulator: insert=5, update=9, delete=17).PhotoManagerNotifyChannelnow overridesonChange(selfChange, uri, flags)and uses the flag as the authoritative change type, keeping the existing date-diff inference only for untagged notifications (API < 30 or plainnotifyChange(uri, null)senders).Additionally:
deleteno longer queries the (already gone) row.galleryIdinstead of being dropped.Verification
Example app with
addChangeCallback+startChangeNotifyon emulators:adb pushfile (pending row)insert(wasdelete) ✅insert(via scan broadcast) ✅content insertcomplete rowinsert+ galleryId ✅content updatefavoriteupdate(heuristic would sayinsert) ✅content deletedelete✅One callback per change in all cases — no duplicates.
Note:
flutter testhas one pre-existing failure (convertMapToAsset reads Android trash state) that also fails on cleanmain, unrelated to this change.